#include "thread.h"
#define STACK_SIZE 1024

GList * waitingThreads = NULL;
Thread runningThread = NULL;
int nextID = 0;


//----------------------------Annexe

void block (void){
  sigset_t set;
  sigemptyset(&set);
  sigaddset(&set, SIGVTALRM);
  sigprocmask(SIG_BLOCK, &set, NULL);
}

void unblock (void){
  sigset_t set;
  sigemptyset(&set);
  sigaddset(&set, SIGVTALRM);
  sigprocmask(SIG_UNBLOCK, &set, NULL);
}

void sigvtalarm (int sig, siginfo_t * si, void * unused){
  printf("1\n");
  thread_yield();
}

void segfault_handler (int sig, siginfo_t * si, void * unused){
  printf("Une erreur de segmentation a ete trouvee avec le thread %p sur l'adresse %p! On alloue la pile d'urgence pour la gestion des signaux !\n", runningThread, si->si_addr);
  stack_t ss;
  if ((ss.ss_sp = malloc(STACK_SIZE)) == NULL){
    printf("Erreur : Mauvaise allocation de la pile de secours\n");
    return;
  }
  ss.ss_size = STACK_SIZE;
  ss.ss_flags = 0;
  if (sigaltstack(&ss, NULL) == -1){
    printf("Erreur : sigaltstack\n");
    return;
  }

  thread_exit(NULL);
}

void reception_signal_default (int sig){
  switch(sig){
  case SIG_KILL:
    thread_exit(NULL);
    break;
  case SIG_YIELD:
    thread_yield();
    break;
  default:
    printf("Signal %d reçu !\n", sig);
  }
}

void exec_func (void){
  void * ret = (runningThread->func)(runningThread->arg);
  thread_exit(ret);
}

int tri (const void * a, const void * b){
  Thread x = (Thread)a;
  Thread y = (Thread)b;

  if (x->priorite_actuelle < y->priorite_actuelle)
    return 1;
  else
    return -1;
}

int init_thread (Thread * t, void * (*fun)(void *), void * funcarg, int prio){
  int pagesize;
  
  if (nextID == 0){
    
    struct sigaction sa;
    sa.sa_flags = SA_SIGINFO;
    sigemptyset(&sa.sa_mask);
    sa.sa_sigaction = segfault_handler;

    if (sigaction(SIGSEGV, &sa, NULL) == -1){
      printf("Erreur : sigaction\n");
      return 1;
    }

    struct sigaction sa2;
    sa2.sa_flags = SA_SIGINFO;
    sigemptyset(&sa2.sa_mask);
    sa2.sa_sigaction = sigvtalarm;

    if (sigaction(SIGVTALRM, &sa2, NULL) == -1){
      printf("Erreur : sigaction\n");
      return 1;
    }

    
    
    Thread m = malloc(sizeof(struct Thread));
    m->contexte = malloc(sizeof(ucontext_t));
    getcontext(m->contexte);
    m->ID = nextID ++;
    m->priorite = 0;
    m->termine = 0;
    m->resultat = NULL;

    runningThread = m;
  }

  if ((pagesize = sysconf(_SC_PAGE_SIZE)) == -1)
    return 1;

  (*t) = malloc(sizeof(struct Thread));

  (*t)->priorite = prio;
  (*t)->priorite_actuelle = prio;

  int i = 0;
  for (i = 0; i < NB_SIG; i ++)
    thread_signal_alloc((*t), i, reception_signal_default);
  (*t)->signal_attente = NULL;

  (*t)->contexte = malloc(sizeof(ucontext_t));
  getcontext((*t)->contexte);
  (*t)->contexte->uc_stack.ss_size = 64 * STACK_SIZE/* * pagesize*/;
  /*(*t)->contexte->uc_stack.ss_sp = memalign(pagesize, STACK_SIZE * pagesize);
  if (mprotect((*t)->contexte->uc_stack.ss_sp, (*t)->contexte->uc_stack.ss_size, PROT_READ | PROT_WRITE | PROT_EXEC) == -1){
    printf("Erreur : mprotect\n");
    return 1;
    }*/
  (*t)->contexte->uc_stack.ss_sp = malloc((*t)->contexte->uc_stack.ss_size);
  (*t)->valgrind_stackid = VALGRIND_STACK_REGISTER((*t)->contexte->uc_stack.ss_sp,
						   (*t)->contexte->uc_stack.ss_sp + 
						   (*t)->contexte->uc_stack.ss_size); 
  (*t)->contexte->uc_link = NULL;
  (*t)->func = fun;
  (*t)->arg = funcarg;
  makecontext((*t)->contexte, exec_func, 1, funcarg);
  
  if ((*t)->contexte->uc_stack.ss_sp == NULL){
    printf("Erreur : Erreur d'allocation de la pile de contexte !\n");
    return 1;
  }
  
  (*t)->ID = nextID ++;
  (*t)->priorite = 0;
  (*t)->termine = 0;
  (*t)->resultat = NULL;

  waitingThreads = g_list_insert_sorted(waitingThreads, (*t), tri);


  return 0;
}





//----------------------------API
void thread_exit (void * ret){
  block();
  runningThread->resultat = ret;
  runningThread->termine = 1;
  unblock();

  thread_yield();
}

Thread thread_self (void){
  if (nextID == 0){
    
    Thread m = malloc(sizeof(struct Thread));
    m->contexte = malloc(sizeof(ucontext_t));
    getcontext(m->contexte);
    m->ID = nextID ++;
    m->priorite = 0;
    m->resultat = NULL;
    
    runningThread = m;
  }

  return runningThread;
}

void thread_start(Thread t){
  Thread exRunning = runningThread;
  
  if (!(runningThread->termine)){
    if (runningThread->priorite_actuelle < 0)
      runningThread->priorite_actuelle = runningThread->priorite;
    else
      runningThread->priorite_actuelle --;
    waitingThreads = g_list_insert_sorted(waitingThreads, runningThread, tri);  
  }
  
  waitingThreads = g_list_remove(waitingThreads, t);
  
  runningThread = t;
  
  thread_signal_treat(t);
  
  swapcontext(exRunning->contexte, t->contexte);
}

int thread_create (Thread * t, void * (*fun)(void *), void * funcarg){
  if (nextID == 0){
    struct itimerval t;
    t.it_value.tv_sec = 0;
    t.it_value.tv_usec = TIME_SLICE * 1000;
    t.it_interval.tv_sec = 0;
    t.it_interval.tv_usec = TIME_SLICE * 1000;

    //setitimer (ITIMER_VIRTUAL, &t, NULL);
  }

  block();
  if (init_thread(t, fun, funcarg, DEFAULT_PRIO))
    return 1;

  thread_start((*t));

  unblock();
  return 0;
}

int thread_create_prio (Thread * t, void *(* fun)(void *), void * funcarg, int prio){
  if (nextID == 0){
    struct itimerval t;
    t.it_value.tv_sec = 0;
    t.it_value.tv_usec = TIME_SLICE * 1000;
    t.it_interval.tv_sec = 0;
    t.it_interval.tv_usec = TIME_SLICE * 1000;
    
    //setitimer (ITIMER_VIRTUAL, &t, NULL);
  }
  
  block();
  if (init_thread(t, fun, funcarg, prio))
    return 1;

  thread_start((*t));
  
  unblock();
  return 0;
}

int thread_yield(void){
  if (waitingThreads != NULL)
    thread_start(((Thread)(g_list_first(waitingThreads)->data)));
  return 0;
}

int thread_join(Thread t, void ** retour){
  if (waitingThreads != NULL){
    while (t->termine != 1)
      thread_start(t);
  }
  
  block();
  *retour = t->resultat;
  
  VALGRIND_STACK_DEREGISTER(t->valgrind_stackid);
  
  free(t->contexte->uc_stack.ss_sp);
  free(t->contexte);
  free(t);
  
  unblock();
  return 0;
}

void thread_kill (Thread t, int sig){
  block();
  if (sig >= 0 && sig < NB_SIG && t != NULL && g_list_find(waitingThreads, t) != NULL){
    int * signal = malloc(sizeof(int));
    *signal = sig;
    t->signal_attente = g_list_append(t->signal_attente, signal);
  }
  unblock();
}

void thread_signal_alloc (Thread t, int sig, void (*func) (int)){
  block();
  if (sig >= 0 && sig < NB_SIG && t != NULL)
    t->fonctions_sig[sig] = func;
  unblock();
}

void thread_signal_treat (Thread t){
  block();
  if (t != NULL){
    GList * elt = t->signal_attente;
    
    while (elt != NULL){
      int * sig = ((int*)(elt->data));
      (t->fonctions_sig[*sig])(*sig);
      elt = elt->next;
    }

    g_list_free_full(t->signal_attente, free);
    t->signal_attente = NULL;
  }
  unblock();
}
